{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# A Quick Introduction to Optuna\n",
    "\n",
    "This Jupyter notebook goes through the basic usage of Optuna.\n",
    "\n",
    "- Install Optuna\n",
    "- Write a training algorithm that involves hyperparameters\n",
    "  - Read train/valid data\n",
    "  - Define and train model\n",
    "  - Evaluate model\n",
    "- Use Optuna to tune the hyperparameters (hyperparameter optimization, HPO)\n",
    "- Visualize HPO"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install `optuna`\n",
    "\n",
    "Optuna can be installed via `pip` or `conda`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install --quiet optuna"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import optuna\n",
    "\n",
    "optuna.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimize Hyperparameters\n",
    "\n",
    "### Define a simple scikit-learn model\n",
    "\n",
    "We start with a simple random forest model to classify flowers in the Iris dataset. We define a function called `objective` that encapsulates the whole training process and outputs the accuracy of the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sklearn.datasets\n",
    "import sklearn.ensemble\n",
    "import sklearn.model_selection\n",
    "\n",
    "def objective():\n",
    "    iris = sklearn.datasets.load_iris()  # Prepare the data.\n",
    "    \n",
    "    clf = sklearn.ensemble.RandomForestClassifier(    \n",
    "        n_estimators=5, max_depth=3)  # Define the model.\n",
    "    \n",
    "    return sklearn.model_selection.cross_val_score(\n",
    "        clf, iris.data, iris.target, n_jobs=-1, cv=3).mean()  # Train and evaluate the model.\n",
    "\n",
    "print('Accuracy: {}'.format(objective()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Optimize hyperparameters of the model\n",
    "\n",
    "The hyperparameters of the above algorithm are `n_estimators` and `max_depth` for which we can try different values to see if the model accuracy can be improved. The `objective` function is modified to accept a trial object. This trial has several methods for sampling hyperparameters. We create a study to run the hyperparameter optimization and finally read the best hyperparameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import optuna\n",
    "\n",
    "def objective(trial):\n",
    "    iris = sklearn.datasets.load_iris()\n",
    "    \n",
    "    n_estimators = trial.suggest_int('n_estimators', 2, 20)\n",
    "    max_depth = int(trial.suggest_float('max_depth', 1, 32, log=True))\n",
    "    \n",
    "    clf = sklearn.ensemble.RandomForestClassifier(\n",
    "        n_estimators=n_estimators, max_depth=max_depth)\n",
    "    \n",
    "    return sklearn.model_selection.cross_val_score(\n",
    "        clf, iris.data, iris.target, n_jobs=-1, cv=3).mean()\n",
    "\n",
    "study = optuna.create_study(direction='maximize')\n",
    "study.optimize(objective, n_trials=100)\n",
    "\n",
    "trial = study.best_trial\n",
    "\n",
    "print('Accuracy: {}'.format(trial.value))\n",
    "print(\"Best hyperparameters: {}\".format(trial.params))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is possible to condition hyperparameters using Python `if` statements. We can for instance include another classifier, a support vector machine, in our HPO and define hyperparameters specific to the random forest model and the support vector machine."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sklearn.svm\n",
    "\n",
    "def objective(trial):\n",
    "    iris = sklearn.datasets.load_iris()\n",
    "\n",
    "    classifier = trial.suggest_categorical('classifier', ['RandomForest', 'SVC'])\n",
    "    \n",
    "    if classifier == 'RandomForest':\n",
    "        n_estimators = trial.suggest_int('n_estimators', 2, 20)\n",
    "        max_depth = int(trial.suggest_float('max_depth', 1, 32, log=True))\n",
    "\n",
    "        clf = sklearn.ensemble.RandomForestClassifier(\n",
    "            n_estimators=n_estimators, max_depth=max_depth)\n",
    "    else:\n",
    "        c = trial.suggest_float('svc_c', 1e-10, 1e10, log=True)\n",
    "        \n",
    "        clf = sklearn.svm.SVC(C=c, gamma='auto')\n",
    "\n",
    "    return sklearn.model_selection.cross_val_score(\n",
    "        clf, iris.data, iris.target, n_jobs=-1, cv=3).mean()\n",
    "\n",
    "study = optuna.create_study(direction='maximize')\n",
    "study.optimize(objective, n_trials=100)\n",
    "\n",
    "trial = study.best_trial\n",
    "\n",
    "print('Accuracy: {}'.format(trial.value))\n",
    "print(\"Best hyperparameters: {}\".format(trial.params))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotting the study\n",
    "\n",
    "Plotting the optimization history of the study."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "optuna.visualization.plot_optimization_history(study)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Plotting the accuracies for each hyperparameter for each trial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "optuna.visualization.plot_slice(study)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Plotting the accuracy surface for the hyperparameters involved in the random forest model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "optuna.visualization.plot_contour(study, params=['n_estimators', 'max_depth'])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
